﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.CompositeUI.Instrumentation;
using Microsoft.Practices.CompositeUI.Properties;
using Microsoft.Practices.ObjectBuilder;

namespace Microsoft.Practices.CompositeUI.Commands
{
    /// <summary>
    /// 	Represents a command that can be fired by several UI Elements.
    /// </summary>
    public class Command : IDisposable, IBuilderAware
    {
        #region Fields & Properties

        private string _name;

        /// <summary>
        /// 	Gets the name of the command.
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
        }

        private CommandStatus _status = CommandStatus.Enabled;

        /// <summary>
        /// 	Gets or sets the status of the <see cref = "Command" />, which controls the 
        /// 	visible/enabled behavior of both the <see cref = "Command" /> and 
        /// 	its associated UI elements.
        /// </summary>
        public CommandStatus Status
        {
            get
            {
                return _status;
            }
            set
            {
                var oldValue = _status;
                _status = value;
                if (oldValue != value)
                {
                    OnChanged();
                    if (TraceSource != null)
                    {
                        TraceSource.TraceInformation(Resources.TraceCommandStatusChanged, _name, _status);
                    }
                }
            }
        }

        private ICommandAdapterMapService _mapService;

        /// <summary>
        /// 	This service is used by <see cref = "Command" /> to create adapters for its invokers.
        /// </summary>
        [ServiceDependency]
        public ICommandAdapterMapService MapService
        {
            set
            {
                _mapService = value;
            }
        }

        private TraceSource _traceSource;

        /// <summary>
        /// 	Used by injection to set the <see cref = "TraceSource" /> to use for tracing.
        /// </summary>
        [ClassNameTraceSource]
        public TraceSource TraceSource
        {
            protected get
            {
                return _traceSource;
            }
            set
            {
                _traceSource = value;
            }
        }

        #endregion

        #region Collection Fields & Properties

        private readonly List<CommandAdapter> _adapters = new List<CommandAdapter>();

        /// <summary>
        /// 	Returns a <see cref = "ReadOnlyCollection{T}" /> with the registered <see cref = "CommandAdapter" />.
        /// </summary>
        public virtual ReadOnlyCollection<CommandAdapter> Adapters
        {
            get
            {
                return new ReadOnlyCollection<CommandAdapter>(_adapters);
            }
        }

        private readonly List<CommandAdapter> _ownedAdapters = new List<CommandAdapter>();

        #endregion

        #region Events

        /// <summary>
        /// 	This event signals that some of the command's properties have changed.
        /// </summary>
        public event EventHandler Changed;

        /// <summary>
        /// 	This event signals that this <see cref = "Command" /> is executed. Handle this event to implement the
        /// 	actions to be executed when the command is fired.
        /// </summary>
        public event EventHandler ExecuteAction;

        /// <summary>
        /// 	Fires the <see cref = "Changed" /> event for this command.
        /// </summary>
        protected virtual void OnChanged()
        {
            var changed = Changed;
            if (changed != null)
            {
                changed(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// 	Handles the <see cref = "CommandAdapter.ExecuteCommand" /> event and fires the <see cref = "ExecuteAction" />
        /// 	event accordingly.
        /// </summary>
        protected virtual void OnExecuteAction()
        {
            var executeAction = ExecuteAction;
            if (Status == CommandStatus.Enabled && executeAction != null)
            {
                executeAction(this, EventArgs.Empty);
            }
        }

        internal bool IsHandlerRegistered(object target, MethodInfo methodInfo)
        {
            var executeAction = ExecuteAction;
            return executeAction != null && executeAction.GetInvocationList().Any(handler => handler.Target == target && handler.Method == methodInfo);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 	Called to free resources.
        /// </summary>
        /// <param name = "disposing">Should be true when calling from Dispose().</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                RemoveCommandAdapters();
            }
        }

        #endregion

        #region Implementation of IBuilderAware

        /// <summary>
        /// 	See <see cref = "IBuilderAware.OnBuiltUp" /> for more information.
        /// </summary>
        public virtual void OnBuiltUp(string id)
        {
            _name = id;
        }

        /// <summary>
        /// 	See <see cref = "IBuilderAware.OnTearingDown" /> for more information.
        /// </summary>
        public virtual void OnTearingDown()
        {
        }

        #endregion

        #region Add & Remove CommandAdapter

        /// <summary>
        /// 	Registers a <see cref = "CommandAdapter" /> with this command.
        /// </summary>
        /// <param name = "adapter">The <see cref = "CommandAdapter" /> to register with this <see cref = "Command" />.</param>
        public virtual void AddCommandAdapter(CommandAdapter adapter)
        {
            adapter.ExecuteCommand += ExecuteActionHandler;
            adapter.BindCommand(this);
            _adapters.Add(adapter);
        }

        /// <summary>
        /// 	Removes the <see cref = "CommandAdapter" /> from this command.
        /// </summary>
        /// <param name = "adapter">The <see cref = "CommandAdapter" /> to remove from this <see cref = "Command" />.</param>
        public virtual void RemoveCommandAdapter(CommandAdapter adapter)
        {
            adapter.ExecuteCommand -= ExecuteActionHandler;
            adapter.UnbindCommand(this);
            _adapters.Remove(adapter);
        }

        private void RemoveCommandAdapters()
        {
            foreach (var adapter in _adapters.ToArray())
            {
                RemoveCommandAdapter(adapter);
            }
            foreach (var adapter in _ownedAdapters.ToArray())
            {
                adapter.Dispose();
            }
        }

        #region Event Handlers

        private void ExecuteActionHandler(object sender, EventArgs e)
        {
            OnExecuteAction();
        }

        #endregion

        #endregion

        #region Find CommandAdapters

        /// <summary>
        /// 	Returns a <see cref = "ReadOnlyCollection{T}" /> with the registered <see cref = "CommandAdapter" />
        /// 	which are of the type specified by TAdapter.
        /// </summary>
        /// <typeparam name = "TAdapter">The type of adapter to search for.</typeparam>
        /// <returns>A new instance of a <see cref = "ReadOnlyCollection{T}" />.</returns>
        public virtual ReadOnlyCollection<TAdapter> FindCommandAdapters<TAdapter>() where TAdapter : CommandAdapter
        {
            return new ReadOnlyCollection<TAdapter>(_adapters.Where(adapter => adapter is TAdapter).Cast<TAdapter>().ToList());
        }

        #endregion

        #region Execute

        /// <summary>
        /// 	Executes this <see cref = "Command" /> by firing the <see cref = "ExecuteAction" /> event.
        /// </summary>
        /// <remarks>
        /// 	The event will only be fired when the command's status is enabled.
        /// </remarks>
        public virtual void Execute()
        {
            if (_status == CommandStatus.Enabled)
            {
                OnExecuteAction();
                if (TraceSource != null)
                {
                    TraceSource.TraceInformation(Resources.TraceCommandExecuted, _name);
                }
            }
        }

        #endregion

        #region Add & Remove Invoker

        /// <summary>
        /// 	Adds a new invoker for this <see cref = "Command" />.
        /// </summary>
        /// <remarks>
        /// 	The command will use the <see cref = "ICommandAdapterMapService" /> to create the 
        /// 	corresponding <see cref = "CommandAdapter" /> for the specified invoker.
        /// </remarks>
        public virtual void AddInvoker(object invoker, string eventName)
        {
            if (_mapService == null)
            {
                throw new CommandException(String.Format(CultureInfo.CurrentCulture, Resources.CannotGetMapService, _name));
            }
            var adapter = _mapService.CreateAdapter(invoker.GetType());
            if (adapter == null)
            {
                throw new CommandException(String.Format(CultureInfo.CurrentCulture, Resources.CannotGetCommandAdapter, invoker.GetType()));
            }
            adapter.AddInvoker(invoker, eventName);
            AddCommandAdapter(adapter);
            _ownedAdapters.Add(adapter);
        }

        /// <summary>
        /// 	Removes the invoker from the <see cref = "Command" />.
        /// </summary>
        /// <param name = "invoker">An invoker object for the command.</param>
        /// <param name = "eventName">The name of the event on the invoker object that fires 
        /// 	this <see cref = "Command" />.</param>
        /// <remarks>
        /// 	This method removes the invoker from all the <see cref = "CommandAdapter" /> 
        /// 	registered with this <see cref = "Command" />.
        /// </remarks>
        public virtual void RemoveInvoker(object invoker, string eventName)
        {
            foreach (var adapter in _adapters.ToArray().Where(adapter => adapter.ContainsInvoker(invoker)))
            {
                adapter.RemoveInvoker(invoker, eventName);
                if (adapter.InvokerCount == 0)
                {
                    RemoveCommandAdapter(adapter);
                    if (_ownedAdapters.Contains(adapter))
                    {
                        _ownedAdapters.Remove(adapter);
                        adapter.Dispose();
                    }
                }
            }
        }

        #endregion

        #region ClassNameTraceSourceAttribute Class

        private class ClassNameTraceSourceAttribute : TraceSourceAttribute
        {
            public ClassNameTraceSourceAttribute() : base(typeof (Command).FullName)
            {
            }
        }

        #endregion
    }
}